﻿using System;
using System.Collections.Generic;
using GeoAPI.Geometries;
using NetTopologySuite.Algorithm;
using NetTopologySuite.Utilities;

namespace NetTopologySuite.Geometries
{
	[Serializable]
	public class Polygon : Geometry, IPolygon, ISurface, IGeometry, ICloneable, IComparable, IComparable<IGeometry>, IEquatable<IGeometry>, IPolygonal
	{
		public Polygon(ILinearRing shell, ILinearRing[] holes) : this(shell, holes, Geometry.DefaultFactory)
		{
		}

		public Polygon(ILinearRing shell, ILinearRing[] holes, IGeometryFactory factory) : base(factory)
		{
            if (shell == null)
            {
                shell = base.Factory.CreateLinearRing((ICoordinateSequence)null);
            }
            if (holes == null)
            {
                holes = new ILinearRing[0];
            }
            if (Geometry.HasNullElements(CollectionUtil.Cast<ILinearRing, object>(holes)))
            {
                throw new ArgumentException("holes must not contain null elements");
            }
            if (shell.IsEmpty && Geometry.HasNonEmptyElements(CollectionUtil.Cast<ILinearRing, IGeometry>(holes)))
            {
                throw new ArgumentException("shell is empty but holes are not");
            }
            this._shell = shell;
            this._holes = holes;
        }

		public override Coordinate Coordinate
		{
			get
			{
				return this._shell.Coordinate;
			}
		}

		public override Coordinate[] Coordinates
		{
			get
			{
				if (this.IsEmpty)
				{
					return new Coordinate[0];
				}
				Coordinate[] array = new Coordinate[this.NumPoints];
				int num = -1;
				Coordinate[] coordinates = this._shell.Coordinates;
				for (int i = 0; i < coordinates.Length; i++)
				{
					num++;
					array[num] = coordinates[i];
				}
				for (int j = 0; j < this._holes.Length; j++)
				{
					Coordinate[] coordinates2 = this._holes[j].Coordinates;
					for (int k = 0; k < coordinates2.Length; k++)
					{
						num++;
						array[num] = coordinates2[k];
					}
				}
				return array;
			}
		}

		public override double[] GetOrdinates(Ordinate ordinate)
		{
			if (this.IsEmpty)
			{
				return new double[0];
			}
			Ordinates ordinates = OrdinatesUtility.ToOrdinatesFlag(new Ordinate[]
			{
				ordinate
			});
			if ((this._shell.CoordinateSequence.Ordinates & ordinates) != ordinates)
			{
				return Geometry.CreateArray(this.NumPoints, double.NaN);
			}
			double[] array = new double[this.NumPoints];
			double[] ordinates2 = this._shell.GetOrdinates(ordinate);
			Array.Copy(ordinates2, 0, array, 0, ordinates2.Length);
			int num = ordinates2.Length;
			ILinearRing[] holes = this._holes;
			for (int i = 0; i < holes.Length; i++)
			{
				ordinates2 = holes[i].GetOrdinates(ordinate);
				Array.Copy(ordinates2, 0, array, num, ordinates2.Length);
				num += ordinates2.Length;
			}
			return array;
		}

		public override int NumPoints
		{
			get
			{
				int num = this._shell.NumPoints;
				for (int i = 0; i < this._holes.Length; i++)
				{
					num += this._holes[i].NumPoints;
				}
				return num;
			}
		}

		public override Dimension Dimension
		{
			get
			{
				return Dimension.Surface;
			}
		}

		public override Dimension BoundaryDimension
		{
			get
			{
				return Dimension.Curve;
			}
		}

		public override bool IsEmpty
		{
			get
			{
				return this._shell.IsEmpty;
			}
		}

		public ILineString ExteriorRing
		{
			get
			{
				return this._shell;
			}
		}

		public int NumInteriorRings
		{
			get
			{
				return this._holes.Length;
			}
		}

		public ILineString[] InteriorRings
		{
			get
			{
				return CollectionUtil.Cast<ILinearRing, ILineString>(this._holes);
			}
		}

		public ILineString GetInteriorRingN(int n)
		{
			return this._holes[n];
		}

		public override string GeometryType
		{
			get
			{
				return "Polygon";
			}
		}

		public override OgcGeometryType OgcGeometryType
		{
			get
			{
				return OgcGeometryType.Polygon;
			}
		}

		public override double Area
		{
			get
			{
				double num = 0.0;
				num += System.Math.Abs(CGAlgorithms.SignedArea(this._shell.CoordinateSequence));
				for (int i = 0; i < this._holes.Length; i++)
				{
					num -= System.Math.Abs(CGAlgorithms.SignedArea(this._holes[i].CoordinateSequence));
				}
				return num;
			}
		}

		public override double Length
		{
			get
			{
				double num = 0.0;
				num += this._shell.Length;
				for (int i = 0; i < this._holes.Length; i++)
				{
					num += this._holes[i].Length;
				}
				return num;
			}
		}

		public override IGeometry Boundary
		{
			get
			{
				if (this.IsEmpty)
				{
					return base.Factory.CreateMultiLineString(null);
				}
				ILinearRing[] array = new ILinearRing[this._holes.Length + 1];
				array[0] = this._shell;
				for (int i = 0; i < this._holes.Length; i++)
				{
					array[i + 1] = this._holes[i];
				}
				if (array.Length <= 1)
				{
					return base.Factory.CreateLinearRing(array[0].CoordinateSequence);
				}
				return base.Factory.CreateMultiLineString(CollectionUtil.Cast<ILinearRing, ILineString>(array));
			}
		}

		protected override Envelope ComputeEnvelopeInternal()
		{
			return this._shell.EnvelopeInternal;
		}

		public override bool EqualsExact(IGeometry other, double tolerance)
		{
			if (!this.IsEquivalentClass(other))
			{
				return false;
			}
			IPolygon polygon = (IPolygon)other;
			IGeometry shell = this._shell;
			IGeometry shell2 = polygon.Shell;
			if (!shell.EqualsExact(shell2, tolerance))
			{
				return false;
			}
			if (this._holes.Length != polygon.Holes.Length)
			{
				return false;
			}
			for (int i = 0; i < this._holes.Length; i++)
			{
				if (!this._holes[i].EqualsExact(polygon.Holes[i], tolerance))
				{
					return false;
				}
			}
			return true;
		}

		public override void Apply(ICoordinateFilter filter)
		{
			this._shell.Apply(filter);
			for (int i = 0; i < this._holes.Length; i++)
			{
				this._holes[i].Apply(filter);
			}
		}

		public override void Apply(ICoordinateSequenceFilter filter)
		{
			((LinearRing)this._shell).Apply(filter);
			if (!filter.Done)
			{
				for (int i = 0; i < this._holes.Length; i++)
				{
					((LinearRing)this._holes[i]).Apply(filter);
					if (filter.Done)
					{
						break;
					}
				}
			}
			if (filter.GeometryChanged)
			{
				base.GeometryChanged();
			}
		}

		public override void Apply(IGeometryFilter filter)
		{
			filter.Filter(this);
		}

		public override void Apply(IGeometryComponentFilter filter)
		{
			filter.Filter(this);
			this._shell.Apply(filter);
			for (int i = 0; i < this._holes.Length; i++)
			{
				this._holes[i].Apply(filter);
			}
		}

		public override object Clone()
		{
			Polygon polygon = (Polygon)base.Clone();
			polygon._shell = (LinearRing)this._shell.Clone();
			polygon._holes = new ILinearRing[this._holes.Length];
			for (int i = 0; i < this._holes.Length; i++)
			{
				polygon._holes[i] = (LinearRing)this._holes[i].Clone();
			}
			return polygon;
		}

		public override IGeometry ConvexHull()
		{
			return this.ExteriorRing.ConvexHull();
		}

		public override void Normalize()
		{
			Polygon.Normalize(this._shell, true);
			ILinearRing[] holes = this.Holes;
			for (int i = 0; i < holes.Length; i++)
			{
				Polygon.Normalize(holes[i], false);
			}
			Array.Sort<ILinearRing>(this._holes);
		}

		protected internal override int CompareToSameClass(object o)
		{
			Geometry geometry = (LinearRing)this._shell;
			ILinearRing shell = ((IPolygon)o).Shell;
			return geometry.CompareToSameClass(shell);
		}

		protected internal override int CompareToSameClass(object other, IComparer<ICoordinateSequence> comparer)
		{
			IPolygon polygon = (IPolygon)other;
			LinearRing linearRing = (LinearRing)this._shell;
			LinearRing o = (LinearRing)polygon.Shell;
			int num = linearRing.CompareToSameClass(o, comparer);
			if (num != 0)
			{
				return num;
			}
			int numInteriorRings = this.NumInteriorRings;
			int numInteriorRings2 = polygon.NumInteriorRings;
			int num2 = 0;
			while (num2 < numInteriorRings && num2 < numInteriorRings2)
			{
				Geometry geometry = (LinearRing)this.GetInteriorRingN(num2);
				LinearRing o2 = (LinearRing)polygon.GetInteriorRingN(num2);
				int num3 = geometry.CompareToSameClass(o2, comparer);
				if (num3 != 0)
				{
					return num3;
				}
				num2++;
			}
			if (num2 < numInteriorRings)
			{
				return 1;
			}
			if (num2 < numInteriorRings2)
			{
				return -1;
			}
			return 0;
		}

		private static void Normalize(ILinearRing ring, bool clockwise)
		{
			if (ring.IsEmpty)
			{
				return;
			}
			Coordinate[] array = new Coordinate[ring.Coordinates.Length - 1];
			Array.Copy(ring.Coordinates, 0, array, 0, array.Length);
			Coordinate firstCoordinate = CoordinateArrays.MinCoordinate(ring.Coordinates);
			CoordinateArrays.Scroll(array, firstCoordinate);
			Array.Copy(array, 0, ring.Coordinates, 0, array.Length);
			ring.Coordinates[array.Length] = array[0];
			if (CGAlgorithms.IsCCW(ring.Coordinates) == clockwise)
			{
				CoordinateArrays.Reverse(ring.Coordinates);
			}
		}

		public override bool IsRectangle
		{
			get
			{
				if (this.NumInteriorRings != 0)
				{
					return false;
				}
				if (this.Shell == null)
				{
					return false;
				}
				if (this.Shell.NumPoints != 5)
				{
					return false;
				}
				ICoordinateSequence coordinateSequence = this.Shell.CoordinateSequence;
				Envelope envelopeInternal = base.EnvelopeInternal;
				for (int i = 0; i < 5; i++)
				{
					double x = coordinateSequence.GetX(i);
					if (x != envelopeInternal.MinX && x != envelopeInternal.MaxX)
					{
						return false;
					}
					double y = coordinateSequence.GetY(i);
					if (y != envelopeInternal.MinY && y != envelopeInternal.MaxY)
					{
						return false;
					}
				}
				double num = coordinateSequence.GetX(0);
				double num2 = coordinateSequence.GetY(0);
				for (int j = 1; j <= 4; j++)
				{
					double x2 = coordinateSequence.GetX(j);
					double y2 = coordinateSequence.GetY(j);
					bool flag = x2 != num;
					bool flag2 = y2 != num2;
					if (flag == flag2)
					{
						return false;
					}
					num = x2;
					num2 = y2;
				}
				return true;
			}
		}

		public override IGeometry Reverse()
		{
			Polygon polygon = (Polygon)this.Clone();
			polygon.Shell = (LinearRing)((LinearRing)this._shell.Clone()).Reverse();
			polygon.Holes = CollectionUtil.Cast<LinearRing, ILinearRing>(new LinearRing[this._holes.Length]);
			for (int i = 0; i < this._holes.Length; i++)
			{
				polygon.Holes[i] = (LinearRing)((LinearRing)this._holes[i].Clone()).Reverse();
			}
			return polygon;
		}

		public Polygon(ILinearRing shell, IGeometryFactory factory) : this(shell, null, factory)
		{
		}

		public Polygon(ILinearRing shell) : this(shell, null, Geometry.DefaultFactory)
		{
		}

		public ILinearRing Shell
		{
			get
			{
				return this._shell;
			}
			private set
			{
				this._shell = value;
			}
		}

		public ILinearRing[] Holes
		{
			get
			{
				return this._holes;
			}
			private set
			{
				this._holes = value;
			}
		}

		public static readonly IPolygon Empty = new GeometryFactory().CreatePolygon(null, null);

		private ILinearRing _shell;

		private ILinearRing[] _holes;
	}
}
